﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows;
using System.Xml.Serialization;
using Kiva7.Constants;
using Kiva7.DataClasses;
using Kiva7.DataResponse;
using Kiva7.Utilities;
using Kiva7.ViewModels;

namespace Kiva7.DataRequests
{
    public abstract class DataRequest<T, TData>
        : INotifyPropertyChanged
        where T : BaseListResponse
        where TData : class
    {

        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (null != handler)
            {
                CheckAndInvoke(() => handler(this, new PropertyChangedEventArgs(propertyName)));
            }
        }

        public EventHandler DataChanged;
        private void OnDataChanged(EventArgs e)
        {
            if (DataChanged != null)
            {
                DataChanged(this, e);
            }
        }


        protected DataRequest(bool responseToData,
                              bool loadAllData)
            : this()
        {
            _responseToData = responseToData;
            _loadAllData = loadAllData;
            _cacheData = true;
        }

        protected DataRequest(bool responseToData,
                              bool loadAllData,
                              bool cacheData)
            : this()
        {
            _responseToData = responseToData;
            _loadAllData = loadAllData;
            _cacheData = cacheData;
        }

        protected DataRequest()
        {
            _data = new DataRequestList<TData>();
        }

        private WebClient _webClient;

        protected const string KivaAppId = Constant.KivaAppId;
        protected const string BaseUrl = Constant.BaseUrl;

        public bool PagesLoading
        {
            get { return PagesLoaded != PagesCount; }
        }

        private int _pagesloaded;

        public int PagesLoaded
        {
            get { return _pagesloaded; }
            set
            {
                if (_pagesloaded != value)
                {
                    _pagesloaded = value;
                    NotifyPropertyChanged("PagesLoaded");
                    NotifyPropertyChanged("PagesLoading");
                }
            }
        }

        private int _pagesCount;
        public int PagesCount
        {
            get { return _pagesCount; }
            set
            {
                if (_pagesCount != value)
                {
                    _pagesCount = value;
                    NotifyPropertyChanged("PagesCount");
                    NotifyPropertyChanged("PagesLoading");
                }
            }
        }

        private bool _isloaded;
        public bool IsLoaded
        {
            get { return _isloaded; }
            set
            {
                if (_isloaded != value)
                {
                    _isloaded = value;
                    NotifyPropertyChanged("IsLoaded");
                }
            }
        }

        private bool _hasError;
        public bool HasError
        {
            get { return _hasError; }
            set
            {
                if (_hasError != value)
                {
                    _hasError = value;
                    NotifyPropertyChanged("HasError");
                }
            }
        }

        private LoadStatus _loadStatus;
        public LoadStatus LoadStatus
        {
            get { return _loadStatus; }
            set
            {
                if (_loadStatus != value)
                {
                    _loadStatus = value;
                    NotifyPropertyChanged("LoadStatus");
                }
            }
        }

        //private Uri lastUrl;

        private readonly DataRequestList<TData> _data;
        public DataRequestList<TData> Data
        {
            get { return _data; }
        }

        private TData _singleData;
        public TData SingleData
        {
            get { return _singleData; }
            set
            {
                //if (_SingleData != value)
                //{
                _singleData = value;
                NotifyPropertyChanged("SingleData");
                //}
            }
        }

        public virtual T CreateDefault()
        {
            return default(T);
        }

        private void HandleDefault(object userToken)
        {
            PagesLoaded = 1;
            PagesCount = 1;
            IsLoaded = true;
            HasError = false;
            LoadStatus = LoadStatus.Loaded;
            SingleData = null;
            Data.Clear();

            T response = CreateDefault();

            CopyResponse(response);

            DoResponse(response, userToken);
        }

        private readonly bool _cacheData;
        private readonly bool _responseToData;
        private readonly bool _loadAllData;

        public void LoadResponse(Uri uri)
        {
            LoadResponse(uri, null);
        }

        public void LoadResponse(Uri uri,
                                 object userToken)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback((stateInfo) =>
                {

                    if (uri == null)
                    {
                        HandleDefault(userToken);
                        return;
                    }
                    try
                    {
                        PagesLoaded = 0;
                        PagesCount = 0;
                        IsLoaded = false;
                        HasError = false;
                        LoadStatus = LoadStatus.Loading;
                        SingleData = null;
                        Data.Clear();
                        Cancel();

                        _webClient = new WebClient();
                        _webClient.DownloadStringCompleted += DownloadStringCompleted;

                        DataRequestUserState data = new DataRequestUserState
                            {
                                UserToken = userToken,
                                OriginalUri = uri,
                                CurrentUri = uri
                            };

                        string result = null;
                        if (_cacheData)
                        {
                            result = DataCache.Current.RetrieveString(uri);
                        }
                        if (string.IsNullOrEmpty(result))
                        {
                            _webClient.DownloadStringAsync(uri, data);
                        }
                        else
                        {
                            HandleString(result, data);
                        }
                    }
                    catch (Exception ex)
                    {
                        ExceptionHandler.HandleException(ex);
                        DoResponse(ex);
                    }
                }));
        }

        private void LoadNextPage(T response, DataRequestUserState userState)
        {
            if (response != null
                && response.Paging != null
                &&
                response.Paging.Page < response.Paging.Pages)
            {
                try
                {
                    UrlCreator uc = new UrlCreator(userState.OriginalUri);
                    uc.Add("page", response.Paging.Page + 1);

                    userState.CurrentUri = uc.ToUri();

                    string result = null;
                    if (_cacheData)
                    {
                        result = DataCache.Current.RetrieveString(userState.CurrentUri);
                    }
                    if (string.IsNullOrEmpty(result))
                    {
                        _webClient.DownloadStringAsync(userState.CurrentUri, userState);
                    }
                    else
                    {
                        HandleString(result, userState);
                    }
                }
                catch (Exception ex)
                {
                    ExceptionHandler.HandleException(ex);
                    DoResponse(ex);
                }
            }
        }

        protected void DownloadStringCompleted(object sender,
                                               DownloadStringCompletedEventArgs e)
        {
            IsLoaded = true;
            LoadStatus = LoadStatus.Loaded;
            if (e.Cancelled)
            {
                DoResponse(e.Cancelled);
            }
            else if (e.Error != null)
            {
                DoResponse(e.Error);
            }
            else
            {
                DataRequestUserState userState = e.UserState as DataRequestUserState;
                if (userState != null)
                {
                    if (_cacheData)
                    {
                        DataCache.Current.StoreString(userState.CurrentUri, e.Result);
                    }
                    HandleString(e.Result, userState);
                }
            }
        }

        private void HandleString(string result, DataRequestUserState userState)
        {
            IsLoaded = true;
            LoadStatus = LoadStatus.Loaded;
            try
            {
                XmlSerializer xs = new XmlSerializer(typeof(T));
                StringReader sr = new StringReader(result);

                T response = xs.Deserialize(sr) as T;

                if (_responseToData)
                {
                    CopyResponse(response);
                }
                DoResponse(response, userState);
                SetPages(response);
                if (_loadAllData)
                {
                    LoadNextPage(response, userState);
                }
            }
            catch (Exception ex)
            {
                ExceptionHandler.HandleException(ex);

                DoResponse(ex);
            }
        }

        private void SetPages(T response)
        {
            if (response != null
                &&
                response.Paging != null)
            {
                PagesCount = response.Paging.Pages;
                PagesLoaded = response.Paging.Page;
            }
        }

        public delegate void DataRequestDelegate(object sender,
                                                 DataRequestEventArgs e);

        public class DataRequestEventArgs : EventArgs
        {
            public DataRequestEventArgs(T response,
                                        object userState)
            {
                Response = response;
                UserState = userState;
            }

            public DataRequestEventArgs(bool cancelled)
            {
                Cancelled = cancelled;
            }

            public DataRequestEventArgs(Exception exception)
            {
                Exception = exception;
            }

            public T Response { get; private set; }

            public bool Cancelled { get; private set; }
            public Exception Exception { get; private set; }

            public object UserState { get; private set; }
        }

        public event DataRequestDelegate OnResponse;

        private void DoResponse(T response,
                        object userToken)
        {
            if (OnResponse != null)
            {
                CheckAndInvoke(() => OnResponse(this, new DataRequestEventArgs(response, userToken)));
            }
        }

        private void DoResponse(T response,
                                DataRequestUserState userState)
        {
            if (OnResponse != null)
            {
                CheckAndInvoke(() => OnResponse(this, new DataRequestEventArgs(response, userState.UserToken)));
            }
        }

        protected void DoResponse(bool cancelled)
        {
            if (OnResponse != null)
            {
                CheckAndInvoke(() => OnResponse(this, new DataRequestEventArgs(cancelled)));
            }
        }

        protected void DoResponse(Exception exception)
        {
            HasError = true;
            ExceptionHandler.HandleException(exception);
            if (OnResponse != null)
            {
                CheckAndInvoke(() => OnResponse(this, new DataRequestEventArgs(exception)));
            }
        }

        public delegate void CheckAndInvokeDelegate();

        private void CheckAndInvoke(CheckAndInvokeDelegate d)
        {
            if (!Deployment.Current.Dispatcher.CheckAccess())
            {
                Deployment.Current.Dispatcher.BeginInvoke(d.Invoke);
            }
            else
            {
                d.Invoke();
            }
        }

        public void Cancel()
        {
            if (_webClient != null)
            {
                _webClient.CancelAsync();
            }
        }

        protected virtual void CopyResponse(T response)
        {
            IList list = Data;
            if (response == null)
            {
                return;
            }
            DataList dataList = response.GetDataList();
            if (dataList == null)
            {
                return;
            }
            IList data = dataList.GetList();
            if (data == null)
            {
                return;
            }
            if (data.Count > 0)
            {
                SingleData = data[0] as TData;
            }
            foreach (TData o in data)
            {
                DataAdding(o);
                list.Add(o);
            }
            OnDataChanged(EventArgs.Empty);
            Data.DoCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            NotifyPropertyChanged("Data");
        }

        public virtual void DataAdding(TData o)
        {
        }


        public void Clear()
        {
            Cancel();

            LoadStatus = DataRequests.LoadStatus.NotLoaded;
            IsLoaded = false;

            SingleData = null;

            Data.Clear();
            NotifyPropertyChanged("Data");
        }

        private class DataRequestUserState
        {
            public object UserToken;
            public Uri OriginalUri;
            public Uri CurrentUri;
        }
    }
}